﻿namespace Contoso.ActiveDirectory {

    #region Usings
    using System;
    using System.Collections.Generic;
    using System.DirectoryServices;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    #endregion

    /// <summary>
    /// http://msdn.microsoft.com/en-us/library/cc223376%28v=prot.10%29.aspx
    /// There can be multiple queryPolicy objects in a forest. 
    /// A DC determines the queryPolicy object that contains its policies according to the following logic:
    /// 
    /// * If the queryPolicyObject attribute is present on the DC's nTDSDSA object, the DC uses the queryPolicy object referenced by it.
    /// * Otherwise, if the queryPolicyObject attribute is present on the nTDSSiteSettings object for the site to which the DC belongs, the DC uses the queryPolicy object referenced by it.
    /// * Otherwise, the DC uses the queryPolicy object whose DN is "CN=Default Query Policy,CN=Query-Policies" 
    /// relative to the nTDSService object 
    /// (for example, "CN=Default Query Policy, CN=Query-Policies, CN=Directory Service, CN=Windows NT, CN=Services" 
    /// relative to the root of the config NC).
    /// </summary>
    public class LdapAdminLimits {

        #region Members
        /// <summary>
        /// The maximum time, in seconds, that a DC waits for the client to send the first request after the DC receives 
        /// a new connection. If the client does not send the first request in this amount of time, the server disconnects 
        /// the client.
        /// </summary>
        public int InitRecvTimeout { get; private set; }
        /// <summary>
        /// The maximum number of concurrent LDAP search operations that are permitted to run at the same time on a DC. 
        /// When this limit is reached, the DC returns a busy / ERROR_DS_ADMIN_LIMIT_EXCEEDED error.
        /// Only applies to Windows 2000
        /// </summary>
        public int MaxActiveQueries { get; private set; }
        /// <summary>
        /// The maximum number of simultaneous LDAP connections that a DC will accept. If a connection comes in after the DC 
        /// reaches this limit, the DC will drop another connection. The connection that is selected to drop is not constrained 
        /// by the protocol and is determined based on the implementation.
        /// </summary>
        public int MaxConnections { get; private set; }
        /// <summary>
        /// The maximum time, in seconds, that the client can be idle before the DC closes the connection. 
        /// If a connection is idle for more than this time, the DC disconnects the client.
        /// </summary>
        public int MaxConnIdleTime { get; private set; }
        /// <summary>
        /// The maximum size, in bytes, of a UDP datagram request that a DC will process. 
        /// Requests that are larger than this value are ignored by the DC.
        /// </summary>
        public int MaxDataGramRecv { get; private set; }
        /// <summary>
        /// The maximum number of outstanding notification search requests (using the LDAP_SERVER_NOTIFICATION_OID control) 
        /// that the DC permits on a single connection. When this limit is reached, the server returns an adminLimitExceeded 
        /// / ERROR_DS_ADMIN_LIMIT_EXCEEDED error to any new notification searches that are requested on that connection.
        /// </summary>
        public int MaxNotificationPerConn { get; private set; }
        /// <summary>
        /// The maximum number of objects that are returned in a single search result, independent of the size of each returned object
        /// To perform a search where the result might exceed this number of objects, the client must specify the paged search control.
        /// </summary>
        public int MaxPageSize { get; private set; }
        /// <summary>
        /// The maximum number of threads per processor that a DC dedicates to listening for network input or output. 
        /// This value also determines the maximum number of threads per processor that can work on LDAP requests at the same time.
        /// </summary>
        public int MaxPoolThreads { get; private set; }
        /// <summary>
        /// The maximum time, in seconds, that a DC will spend on a single search. When this limit is reached, the DC returns 
        /// a timeLimitExceeded / ERROR_INVALID_PARAMETER error.
        /// </summary>
        public int MaxQueryDuration { get; private set; }
        /// <summary>
        /// The maximum size, in bytes, of a request that the server will accept. 
        /// If the server receives a request that is larger than this, it will drop the connection.
        /// </summary>
        public int MaxReceiveBuffer { get; private set; }
        /// <summary>
        /// The maximum number of bytes that a DC stores to optimize the individual searches that make up a paged search. 
        /// The data that is stored is outside the state model and is implementation-specific.
        /// </summary>
        public int MaxResultSetSize { get; private set; }
        /// <summary>
        /// The maximum number of rows that a DC will create in a temporary database 
        /// table to hold intermediate results during query processing.
        /// </summary>
        public int MaxTempTableSize { get; private set; }
        /// <summary>
        /// The maximum number of values that can be retrieved from a multivalued attribute in a single search request. 
        /// Windows 2000 DCs do not support this policy and instead always use a setting of 1000 values.
        /// </summary>
        public int MaxValRange { get; private set; } 
        #endregion

        #region Constructor
        public LdapAdminLimits() {
            // set defaults
            this.InitRecvTimeout = 120;
            this.MaxActiveQueries = 20;
            this.MaxConnections = 5000;
            this.MaxConnIdleTime = 900;
            this.MaxDataGramRecv = 4096;
            this.MaxNotificationPerConn = 5;
            this.MaxPageSize = 1000;
            this.MaxPoolThreads = 4;
            this.MaxQueryDuration = 120;
            this.MaxReceiveBuffer = 10485760;
            this.MaxResultSetSize = 262144;
            this.MaxTempTableSize = 10000;
            this.MaxValRange = 1500;

            string dn = string.Format("LDAP://CN=Default Query Policy,CN=Query-Policies,CN=Directory Service,CN=Windows NT,CN=Services,{0}", ADDataAccess.ForestRootConfigurationNamingContext);
            string adjustedBindPath = ADDataAccess.GetAdjustedBindPath(dn, PartitionScope.LocalPartition);

            var authenticationType = ADDataAccess.GetAdjustedAuthenticationType(adjustedBindPath);
            using (var directoryEntry = new DirectoryEntry(adjustedBindPath, null, null, authenticationType)) {
                
                if (directoryEntry.Properties.Contains("lDAPAdminLimits")) {
                    for (int index = 0; index < directoryEntry.Properties["lDAPAdminLimits"].Count; index++) {
                        string listEntry = directoryEntry.Properties["lDAPAdminLimits"][index].ToString().Trim();
                        if (!string.IsNullOrWhiteSpace(listEntry) && (listEntry.IndexOf("=") != -1) && (listEntry.IndexOf("=") < listEntry.Length)) {
                            string propertyName = listEntry.Substring(0, listEntry.IndexOf("=")).ToLowerInvariant();
                            string propertyValueText = listEntry.Substring(listEntry.IndexOf("=") + 1);
                            int propertyValue = Convert.ToInt32(propertyValueText);

                            switch (propertyName) {
                                case "initrecvtimeout":
                                    this.InitRecvTimeout = propertyValue;
                                    break;
                                case "maxactivequeries":
                                    this.MaxActiveQueries = propertyValue;
                                    break;
                                case "maxconnections":
                                    this.MaxConnections = propertyValue;
                                    break;
                                case "maxconnidletime":
                                    this.MaxConnIdleTime = propertyValue;
                                    break;
                                case "maxdatagramrecv":
                                    this.MaxDataGramRecv = propertyValue;
                                    break;
                                case "maxnotificationperconn":
                                    this.MaxNotificationPerConn = propertyValue;
                                    break;
                                case "maxpagesize":
                                    this.MaxPageSize = propertyValue;
                                    break;
                                case "maxpoolthreads":
                                    this.MaxPoolThreads = propertyValue;
                                    break;
                                case "maxqueryduration":
                                    this.MaxQueryDuration = propertyValue;
                                    break;
                                case "maxreceivebuffer":
                                    this.MaxReceiveBuffer = propertyValue;
                                    break;
                                case "maxresultsetsize":
                                    this.MaxResultSetSize = propertyValue;
                                    break;
                                case "maxtemptablesize":
                                    this.MaxTempTableSize = propertyValue;
                                    break;
                                case "maxvalrange":
                                    this.MaxValRange = propertyValue;
                                    break;
                            }
                        }
                    }
                }
            }
        } 
        #endregion
    }
}
